/* The Borland BGI text output is completely useless because it clips based
   on character coordinates and not on pixels so we have to make a special
   text output function here. sigh. */

#include "depui/depui.h"

#ifdef MX_PLATFORM_TURBOC

#   define MXMODULE_DRS

#   include <dos.h>
#   include <conio.h>
#   include <assert.h>
#   include <graphics.h>
#   include <string.h>

static MX_PLATFORM_FONT mx__bgi_font[] = {
	{0, "fixed 8x8"},
	{0, "fixed 8x14"},
	{0, "fixed 8x16"},
};

#   define MX__ARRAY(a) (sizeof(a)/sizeof(a[0]))

const MX_PLATFORM_FONT *mx_platform_font_system(const int i)
{
	if (i < 0)
		return &mx__bgi_font[0];

	if (i < (int) MX__ARRAY(mx__bgi_font))
		return &mx__bgi_font[i];

	return 0;
}

void *mx_platform_font_load(const char *filename)
{
	(void) filename;
	return 0;
}

const char *mx_platform_font_path(void)
{
	return 0;
}

void mx_platform_font_free(void *font)
{
	(void) font;
}

unsigned int mx_platform_font_height(void *font)
{
	if (font == mx__bgi_font[1].font)
		return 14;

	if (font == mx__bgi_font[2].font)
		return 16;

	return 8;
}

unsigned int mx_platform_font_width(void *font, const char *text, int len)
{
	(void) font;

	if (len < 0)
		return 8 * strlen(text);

	return len * 8;
}

static const MX_PLATFORM_RESOLUTION mx__bgi_resolutions[] = {
	{640, 480, "640 x 480"},
	{0, 0, 0}
};

static const MX_PLATFORM_DEPTH mx__bgi_depths[] = {
	{4, "4"},
	{0, 0}
};

static const MX_PLATFORM_DRIVER mx__bgi_drivers[] = {
	{"", "none"},
	{0, 0}
};

extern const MX_THEME mx_theme_bgi;

static MX_PLATFORM_THEME mx__bgi_themes[] = {
	{&mx_theme_bgi, "turboc theme"},
	{0, 0},
};

void mx_platform_modes(const MX_PLATFORM_DRIVER * d[], const MX_PLATFORM_RESOLUTION * r[], const MX_PLATFORM_DEPTH * c[],
					   const MX_PLATFORM_THEME * t[])
{
	*d = mx__bgi_drivers;
	*r = mx__bgi_resolutions;
	*c = mx__bgi_depths;
	*t = mx__bgi_themes;
}

static unsigned mx__turboc_mousevalid = false;
static MX_RECT mx__turboc_screen;
static MX_RECT mx__turboc_clip;
static int mx__turboc_offx = 0;
static int mx__turboc_offy = 0;

unsigned mx_platform_start(int w, int h, int c, const void *driver)
{
	union REGS r;
	struct REGPACK reg;

	/* request auto detection */
	int gdriver = DETECT, gmode, errorcode;

	(void) w;
	(void) h;
	(void) c;
	(void) driver;

	/* initialize graphics and local variables */
	initgraph(&gdriver, &gmode, "");

	/* read result of initialization */
	errorcode = graphresult();
	if (errorcode != grOk) {
		printf("Graphics error: %s\n", grapherrormsg(errorcode));
		printf("Press any key to halt:");
		getch();
		exit(1);				/* terminate with an error code */
	}

	/* Reset the mouse driver */
	mx__turboc_mousevalid = false;
	r.x.ax = 0x0000;
	int86(0x33, &r, &r);
	if (r.x.ax) {
		mx__turboc_mousevalid = true;

		/* Show the mouse */
		r.x.ax = 0x0001;
		int86(0x33, &r, &r);
	}

	/* Get some DOS font pointers */
	reg.r_ax = 0x1130;
	reg.r_bx = 0x0300;			/* 8x8 font */
	intr(0x10, &reg);
	mx__bgi_font[0].font = MK_FP(reg.r_es, reg.r_bp);

	assert(mx__bgi_font[0].font);

	reg.r_ax = 0x1130;
	reg.r_bx = 0x0200;			/* 8x14 font */
	intr(0x10, &reg);
	mx__bgi_font[1].font = MK_FP(reg.r_es, reg.r_bp);

	reg.r_ax = 0x1130;
	reg.r_bx = 0x0600;			/* 8x16 font */
	intr(0x10, &reg);
	mx__bgi_font[2].font = MK_FP(reg.r_es, reg.r_bp);

	mx__turboc_screen.x1 = 0;
	mx__turboc_screen.y1 = 0;
	mx__turboc_screen.x2 = getmaxx() - 1;
	mx__turboc_screen.y2 = getmaxy() - 1;

	mx_drs_area(mx__turboc_screen.x2, mx__turboc_screen.y2);

	setfillstyle(SOLID_FILL, LIGHTBLUE);
	setlinestyle(SOLID_LINE, 0xffff, 1);
	setviewport(mx__turboc_screen.x1, mx__turboc_screen.y1, mx__turboc_screen.x2, mx__turboc_screen.y2, 1);

	return true;
}

void mx_platform_stop(void)
{
	union REGS regs;

	/* Hide the mouse */
	regs.x.ax = 0x0002;
	int86(0x33, &regs, &regs);

	closegraph();
	textmode(C80);
}

unsigned mx_platform_clip(const MX_RECT * r)
{
	mx__turboc_offx = r->x1;
	mx__turboc_offy = r->y1;
	mx__turboc_clip = *r;

	setviewport(r->x1, r->y1, r->x2, r->y2, 1);

	return true;
}

const MX_RECT *mx_platform_rect(void)
{
	return &mx__turboc_screen;
}

static void mx__turboc_flush(MX_RECT * rect)
{
	union REGS regs;

	/* Hide the mouse */
	regs.x.ax = 0x0002;
	int86(0x33, &regs, &regs);

	mx__gui_redraw(rect);

	/* Show the mouse */
	regs.x.ax = 0x0001;
	int86(0x33, &regs, &regs);
}

unsigned mx_platform_poll(void)
{
	mx_drs_update(mx__turboc_flush);
	return true;
}

void mx_platform_dirty(const MX_RECT * rect)
{
	mx_drs_dirty(rect, true);
}

unsigned mx_platform_pointer(int *px, int *py, int *pb)
{
	union REGS regs;

	if (!mx__turboc_mousevalid)
		return false;

	/* Get mouse button data */
	regs.x.ax = 0x0003;
	int86(0x33, &regs, &regs);
	*pb = regs.x.bx;
	*px = regs.x.cx;
	*py = regs.x.dx;

	return true;
}

unsigned mx_platform_key(int *scan, int *ascii)
{
	if (!kbhit())
		return false;

	*scan = 0;
	*ascii = getch();

	if (*ascii == 0)
		*scan = getch();

	return true;
}

static unsigned mx_theme_bgi_start(void)
{
	return true;
}

static void mx_theme_bgi_stop(void)
{
}

static void mx_theme_bgi_event(void)
{
	mx__event_default();
}

static void mx__turboc_block(const MX_RECT * objrect, int fill)
{
	setfillstyle(SOLID_FILL, fill);
	bar(objrect->x1 - mx__turboc_offx, objrect->y1 - mx__turboc_offy, objrect->x2 - mx__turboc_offx, objrect->y2 - mx__turboc_offy);
}

static void mx__turboc_frame(const MX_RECT * objrect, int edge, int fill)
{
	setcolor(edge);
	rectangle(objrect->x1 - mx__turboc_offx, objrect->y1 - mx__turboc_offy, objrect->x2 - mx__turboc_offx, objrect->y2 - mx__turboc_offy);

	setfillstyle(SOLID_FILL, fill);
	bar(objrect->x1 - mx__turboc_offx + 1, objrect->y1 - mx__turboc_offy + 1, objrect->x2 - mx__turboc_offx - 1, objrect->y2 - mx__turboc_offy - 1);
}

static void mx_theme_bgi_obj(MX_OBJ * obj)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(obj);

		mx__turboc_frame(objrect, WHITE, LIGHTGRAY);
	}
}

static void mx_theme_bgi_vslider(MX_SLIDER * slider)
{
	if (mx_exposing()) {
		int fill = WHITE;
		MX_RECT objrect = *MXRECT(slider);

		mx__turboc_frame(&objrect, DARKGRAY, LIGHTGRAY);

		if (mx_armed(slider))
			fill = GREEN;

		objrect.y2 = objrect.y1 + slider->base.slider._lower;
		objrect.y1 += slider->base.slider._upper;

		mx__turboc_frame(&objrect, BLACK, fill);
	}
}

static void mx_theme_bgi_hslider(MX_SLIDER * slider)
{
	if (mx_exposing()) {
		int fill = WHITE;
		MX_RECT objrect = *MXRECT(slider);

		mx__turboc_frame(&objrect, DARKGRAY, LIGHTGRAY);

		if (mx_armed(slider))
			fill = GREEN;

		objrect.x2 = objrect.x1 + slider->base.slider._lower;
		objrect.x1 += slider->base.slider._upper;

		mx__turboc_frame(&objrect, BLACK, fill);
	}
}

static void mx__bitchar(const MX_RECT * charrect, const MX_RECT * drawrect, const unsigned char *data, unsigned int width, int fore)
{
	int r;
	unsigned int bpix;
	unsigned int xoff, yoff, x2off;

	xoff = drawrect->x1 - charrect->x1;
	yoff = drawrect->y1 - charrect->y1;
	x2off = charrect->x2 - drawrect->x2;

	/* Move the data over to correct to the mx_clipping on the top and the
	   width of the character */
	data += ((yoff * width) / 8) - 1;

	for (r = drawrect->y1; r <= drawrect->y2; r++) {
		int iter = drawrect->x1;

		/* Move the data over to correct for the clipping on the left */
		bpix = 0x100 >> (xoff % 8);
		data += (xoff / 8) + 1;

		while (iter <= drawrect->x2) {

			/* Go one pixel to right, and maybe to the next data byte */
			bpix >>= 1;
			if (!bpix) {
				bpix = 0x80;
				++data;
			}

			/* Foreground pixel */
			if ((*data) & bpix)
				putpixel(iter - mx__turboc_offx, r - mx__turboc_offy, fore);

			++iter;
		}

		/* Skip data to the right if the right side is clipped */
		data += (x2off / 8);
	}
}

static void mx__outtext(const void *font, const char *text, int len, int x, const int y, int fore)
{
	int n = 0;
	int h = 8;

	MX_RECT charrect;

	if (!font)
		font = mx__bgi_font[0].font;
	assert(font);

	if (font == mx__bgi_font[1].font)
		h = 14;
	else if (font == mx__bgi_font[2].font)
		h = 16;

	charrect.y1 = y;

	/* Make sure we know about text length for zero terminated */
	if (len < 0)
		len = 0x7fff;

	/* Skip easy clipping conditions */
	if ((y + h < mx__turboc_clip.y1) || (y > mx__turboc_clip.y2)
		|| (x > mx__turboc_clip.x2))
		return;

	/* Write each character of the string */
	while ((*text) && (n < len)) {
		const int c = *text;
		MX_RECT drawrect;
		const unsigned char *cdata;

		/* If left side of char farther than right side of clipping, we are
		   done */
		if (x > mx__turboc_clip.x2)
			return;

		cdata = (const unsigned char *) font + c * h;

		charrect.x1 = x;
		charrect.x2 = x + 8 - 1;
		charrect.y2 = y + h - 1;

		/* Only draw characters that are not completely clipped */
		MXRECT_INTERSECT(charrect, mx__turboc_clip, drawrect);
		if (MXRECT_VALID(drawrect))
			mx__bitchar(&charrect, &drawrect, cdata, 8, fore);

		x = charrect.x2 + 1;
		++text;
		++n;
	}
}

static void mx__textchunks(const MX_TEXTUAL_DATA * textual, int x1, int y1, int x2, int y2, int offsetx, int offsety, int fore)
{
	int offx = 0, offy = 0;
	MX__TEXTCHUNK *chunk = mx_dllist_first(textual);
	void *chunkfont = mx__textual_platform_font(textual);

	/* Left and top alignment is correct with offset 0 */
	if (textual->_align & MX_ALIGN_RIGHT)
		offx = mx_w(textual) - textual->_textwidth - 2 * offsetx;
	if (textual->_align & MX_ALIGN_HCENTER)
		offx = (mx_w(textual) - textual->_textwidth - 2 * offsetx) / 2;

	if (textual->_align & MX_ALIGN_BOTTOM)
		offy = mx_h(textual) - textual->_textheight - 2 * offsety;
	if (textual->_align & MX_ALIGN_VCENTER)
		offy = (mx_h(textual) - textual->_textheight - 2 * offsety) / 2;

	while (chunk) {
		int len = 0;
		const char *text = mx_string_text(&chunk->text, &len);

		if (chunk->cursor) {
			if (mx_focused(textual))
				setfillstyle(SOLID_FILL, GREEN);
			else
				setfillstyle(SOLID_FILL, LIGHTGRAY);

			bar(x1 - mx__turboc_offx + chunk->rect.x1, y1 - mx__turboc_offy + chunk->rect.y1, x1 - mx__turboc_offx + chunk->rect.x2,
				y1 - mx__turboc_offy + chunk->rect.y2);
		}

		if (text) {
			mx__outtext(chunkfont, text, len, x1 + chunk->rect.x1 + offsetx + offx, y1 + chunk->rect.y1 + offsety + offy, fore);
		}

		chunk = mx_dllist_next(chunk);
	}
}

static int mx__textback(const MX_TEXTUAL_DATA * textual, int x1, int y1, int x2, int y2, unsigned armable)
{
	int fore = BLACK;
	int back = WHITE;

	if (mx_selected(textual)) {
		back = GREEN;
		fore = WHITE;
	}

	if (mx_disabled(textual))
		fore = DARKGRAY;

	if ((armable) && (mx_armed(textual)))
		back = LIGHTGRAY;

	setfillstyle(SOLID_FILL, back);
	bar(x1 - mx__turboc_offx, y1 - mx__turboc_offy, x2 - mx__turboc_offx, y2 - mx__turboc_offy);

	return fore;
}

static void mx_theme_bgi_textual(MX_TEXTUAL * text)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(text);

		int fore = mx__textback(MXTEXTUAL(text), objrect->x1, objrect->y1,
								objrect->x2, objrect->y2, false);

		mx__textchunks(MXTEXTUAL(text), objrect->x1, objrect->y1, objrect->x2, objrect->y2, 0, 0, fore);
	}
}

static void mx_theme_bgi_scrollcorner(MX_OBJ * scrollcorner)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(scrollcorner);

		mx__turboc_block(objrect, LIGHTGRAY);
	}
}

static void mx_theme_bgi_scroll(MX_SCROLL * scroll)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(scroll);

		mx__turboc_block(objrect, WHITE);
	}
}

static void mx_theme_bgi_button(MX_BUTTON * button)
{
	if (mx_exposing()) {
		int fore;
		const MX_RECT *objrect = MXRECT(button);
		int offset = 0;

		if (mx_selected(button))
			offset = 1;

		if (mx_armed(button)) {
			setcolor(RED);
			rectangle(objrect->x1 - mx__turboc_offx, objrect->y1 - mx__turboc_offy, objrect->x2 - mx__turboc_offx, objrect->y2 - mx__turboc_offy);

		} else if (mx_selected(button)) {
			setcolor(WHITE);
			rectangle(objrect->x1 - mx__turboc_offx, objrect->y1 - mx__turboc_offy, objrect->x2 - mx__turboc_offx, objrect->y2 - mx__turboc_offy);

		} else {
			setcolor(DARKGRAY);
			rectangle(objrect->x1 - mx__turboc_offx, objrect->y1 - mx__turboc_offy, objrect->x2 - mx__turboc_offx, objrect->y2 - mx__turboc_offy);
		}

		fore = mx__textback(MXTEXTUAL(button), objrect->x1 + 1, objrect->y1 + 1, objrect->x2 - 1, objrect->y2 - 1, true);

		mx__textchunks(MXTEXTUAL(button), objrect->x1 + 1 + offset, objrect->y1 + 1 + offset, objrect->x2 - 1, objrect->y2 - 1, offset, offset, fore);
	}
}

void mx_theme_bgi_listelem(MX_LISTELEM * listelem)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(listelem);

		int fore = mx__textback(MXTEXTUAL(listelem), objrect->x1, objrect->y1,
								objrect->x2, objrect->y2, true);

		mx__textchunks(MXTEXTUAL(listelem), objrect->x1, objrect->y1, objrect->x2, objrect->y2, 0, 0, fore);
	}
}

static void mx_theme_bgi_root(MX_WIN * root)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(root);

		mx__turboc_block(objrect, LIGHTGRAY);
	}
}

static void mx_theme_bgi_winborder(MX_WINBORDER * border)
{
	if (mx_exposing()) {
		MX_WIN *owner = MXOBJ(border)->_win;
		const MX_RECT *objrect = MXRECT(border);
		const int x1 = objrect->x1;
		const int y1 = objrect->y1;
		const int x2 = objrect->x2;
		const int y2 = objrect->y2;
		int fore = BLACK;
		int fill = WHITE;

		if (mx_active(owner))
			fill = GREEN;

		if (mx_disabled(owner))
			fore = LIGHTGRAY;

		setcolor(BLACK);
		setfillstyle(SOLID_FILL, fill);

		rectangle(x1 - mx__turboc_offx, y1 - mx__turboc_offy, x2 - mx__turboc_offx, y2 - mx__turboc_offy);

		bar(x1 + 1 - mx__turboc_offx, y1 + 1 - mx__turboc_offy, x2 - 1 - mx__turboc_offx, mx_y1(owner) - 2 - mx__turboc_offy);
		line(x1 + 1 - mx__turboc_offx, mx_y1(owner) - 1 - mx__turboc_offy, x2 - 1 - mx__turboc_offx, mx_y1(owner) - 1 - mx__turboc_offy);
		bar(x1 + 1 - mx__turboc_offx, mx_y2(owner) + 1 - mx__turboc_offy, x2 - 1 - mx__turboc_offx, y2 - 1 - mx__turboc_offy);

		if (owner)
			mx__textchunks(MXTEXTUAL(owner), x1 + 2, y1 + 2, x2 - 2, y2 - 2, 0, 0, fore);
	}
}

static void mx_theme_bgi_win(MX_WIN * win)
{
	if (mx_exposing()) {
		const MX_RECT *objrect = MXRECT(win);

		mx__turboc_block(objrect, LIGHTGRAY);
	}
}

const MX_THEME mx_theme_bgi = {
	mx_theme_bgi_start,
	mx_theme_bgi_stop,
	mx_theme_bgi_event,
	mx_theme_bgi_obj,
	mx_theme_bgi_vslider,
	mx_theme_bgi_hslider,
	mx_theme_bgi_textual,
	mx_theme_bgi_scrollcorner,
	mx_theme_bgi_scroll,
	mx_theme_bgi_button,
	mx_theme_bgi_listelem,
	mx_theme_bgi_root,
	mx_theme_bgi_winborder,
	mx_theme_bgi_win
};

#endif
